package org.dromara.system.service.impl;

import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.google.common.collect.Lists;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.BeanUtils;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.utils.EncryptUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.system.constants.OrderConstants;
import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.PayGroupTypeQueryBo;
import org.dromara.system.domain.bo.config.ApiConfig;
import org.dromara.system.domain.bo.config.CustomField;
import org.dromara.system.domain.bo.PayGroupTypeBo;
import org.dromara.system.domain.vo.*;
import org.dromara.system.kernel.PayPolicyKernel;
import org.dromara.system.kernel.RedisAlgorithmKernel;
import org.dromara.system.mapper.PayGroupAccountMapper;
import org.dromara.system.mapper.PayPolicyMapper;
import org.dromara.system.mapper.PayTypeMapper;
import org.dromara.system.service.IPayAccountService;
import org.dromara.system.service.IPayGroupAccountService;
import org.dromara.system.service.IPayRateService;
import org.dromara.system.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;
import org.dromara.system.mapper.PayGroupTypeMapper;
import org.dromara.system.service.IPayGroupTypeService;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 轮询组支付类型关联Service业务层处理
 *
 * @author Gavino
 * @date 2024-10-22
 */
@RefreshScope
@RequiredArgsConstructor
@Service
public class PayGroupTypeServiceImpl implements IPayGroupTypeService {

    private final PayTypeMapper payTypeMapper;
    private final PayGroupTypeMapper baseMapper;
    private final PayGroupAccountMapper payGroupAccountMapper;
    private final PayPolicyMapper policyMapper;
    private final IPayAccountService payAccountService;
    private final IPayGroupAccountService payGroupAccountService;
    private final RedisAlgorithmKernel redisAlgorithmKernel;
    private final IPayRateService payRateService;
    private final PayPolicyKernel payPolicyKernel;
    @Value("${gatewayDomain}")
    public String gatewayDomain;
    @Value("${fixedKeyTime}")
    public Integer fixedKeyTime;
    public List<Integer> innerLinks = Lists.newArrayList(0, 1, 2);

    /**
     * 获取应用配置的支付平台
     *
     * @param groupId     应用ID
     * @param payTypeCode 支付平台标识
     * @param bo          轮询收款账号参数
     * @return 支付平台列表
     */
    @Override
    public R<List<PayTypeOutVo>> selectPayGroupType(Long groupId, String payTypeCode, PayGroupTypeQueryBo bo) {

        //获取汇率
        if (bo.getUsdMoney() == null) {
            BigDecimal rates = new BigDecimal(1);
            if (!bo.getCurrency().equals("USD")) {
                PayRate payRate = payRateService.selectByCurrency(bo.getCurrency());
                if (payRate == null) {
                    return R.fail("No rate info");//没有获取到汇率信息
                }
                rates = new BigDecimal(payRate.getValue());
            }

            //转成美元金额
            BigDecimal money = new BigDecimal(bo.getTotal()).divide(rates, 2, RoundingMode.HALF_UP);
            bo.setUsdMoney(money);
        }

        //查询轮询组平台
        LambdaQueryWrapper<PayGroupType> query = new LambdaQueryWrapper<>();
        query.eq(PayGroupType::getGroupId, groupId);
        if (StringUtils.isNotEmpty(payTypeCode)) {
            query.eq(PayGroupType::getCode, payTypeCode);
        }
        query.orderByDesc(PayGroupType::getSort);
        List<PayGroupTypeVo> typeVos = baseMapper.selectVoList(query, PayGroupTypeVo.class);
        if (typeVos.isEmpty()) {
            return R.fail("no config payType");
        }

        //查询风控策略
        Map<Long, PayPolicyVo> policyMap = new HashMap<>();
        Set<Long> policyIds = typeVos.stream().map(PayGroupTypeVo::getPolicyId).collect(Collectors.toSet());
        if (!policyIds.isEmpty()) {
            policyMapper.selectVoBatchIds(policyIds).forEach(policy -> policyMap.put(policy.getPolicyId(), policy));
        }

        //风险策略 国家校验
        if (!policyMap.isEmpty()) {
            typeVos = typeVos.stream().filter(typeVo -> {
                PayPolicyVo policy = policyMap.get(typeVo.getPolicyId());
                if (policy == null) {
                    return true;
                }
                return R.isSuccess(payPolicyKernel.validateCountry(policy, bo.getCountry()));
            }).toList();
        }
        if (typeVos.isEmpty()) {
            return R.fail("No payment methods available");
        }

        //查询轮询组收款账号
        LambdaQueryWrapper<PayGroupAccount> query1 = new LambdaQueryWrapper<>();
        query1.eq(PayGroupAccount::getGroupId, groupId);//该轮询组
        query1.in(PayGroupAccount::getStatus, 1, 2);//在线状态/下线
        List<PayGroupAccount> groupAccounts = payGroupAccountMapper.selectList(query1);
        if (groupAccounts.isEmpty()) {
            return R.fail("no online account");
        }

        Map<Long, List<PayGroupAccount>> accountMap = groupAccounts.stream().collect(Collectors.groupingBy(PayGroupAccount::getGroupTypeId));

        //查询平台配置
        Set<Long> typeIds = typeVos.stream().map(PayGroupTypeVo::getTypeId).collect(Collectors.toSet());
        List<PayType> types = payTypeMapper.selectBatchIds(typeIds);
        Map<Long, PayType> typeMap = types.stream().collect(Collectors.toMap(PayType::getTypeId, payType -> payType));

        //是否传了邮箱
        boolean hasEmail = StringUtils.isNotEmpty(bo.getEmail()) || StringUtils.isNotEmpty(bo.getIp());

        //处理轮询逻辑
        List<PayTypeOutVo> outs = new ArrayList<>();
        typeVos.forEach(groupType -> {
            String key = null;
            if (Objects.equals(groupType.getFixedType(), 1)) {
                key = bo.getEmail();
            }
            else if (Objects.equals(groupType.getFixedType(), 2)) {
                key = bo.getIp();
            }
            boolean isFixed = hasEmail && StringUtils.isNotEmpty(key) && Objects.equals(groupType.getIsFixed(), 1) && Objects.equals(groupType.getIsLoop(), 1);//是否开启防止关联
            String fixedKey = OrderConstants.pay_fixed_account + groupType.getId() + ":" + key;
            PayType type = typeMap.get(groupType.getTypeId());//获取账号平台配置

            //此平台轮询组账号
            List<PayGroupAccount> accounts = accountMap.get(groupType.getId());
            if (accounts == null) {
                return;
            }

            //输出对象
            PayTypeOutVo out = BeanUtils.copy(groupType, PayTypeOutVo.class);
            if (out.getDescribes() == null) {
                out.setDescribes("");
            }
            if (out.getLogo() == null) {
                out.setLogo("");
            }

            //防止关联
            if (isFixed && RedisUtils.hasKey(fixedKey)) {
                //邮箱绑定收款平台的一个收款账号，缓存一小时
                Object groupActId = RedisUtils.getCacheObject(fixedKey);
                List<PayGroupAccount> tmpActs = accounts.stream().filter(tmp -> Objects.equals(tmp.getId(), groupActId)).toList();
                if (!tmpActs.isEmpty()) {
                    //构建收款账号输出对象
                    Map<String, Object> account = buildOutGroupAccount(tmpActs.get(0), type, bo);
                    //有可用账号
                    if (account != null) {
                        out.setAccount(account);
                        outs.add(out);
                        return;
                    }
                }
            }

            //有在线账号
            accounts = accounts.stream().filter(tmp -> tmp.getStatus() == 1).toList();
            if (!accounts.isEmpty() && type != null) {
                //轮询获取收款账号
                Map<String, Object> account = accountLoop(type, groupType, accounts, bo);

                //有可用账号
                if (account != null) {
                    out.setAccount(account);
                    outs.add(out);

                    //防止关联
                    if (isFixed) {
                        //邮箱绑定收款平台的一个收款账号，缓存一小时
                        RedisUtils.setCacheObject(fixedKey, account.get("id"), Duration.ofHours(fixedKeyTime));
                    }
                }
            }
        });

        if (outs.isEmpty()) {
            return R.fail("no account available");
        }
        return R.ok(outs);
    }

    /**
     * 轮询获取收款账号
     *
     * @param type      平台配置
     * @param groupType 轮询组配置
     * @param accounts  轮询组账号列表
     * @param bo        轮询收款账号参数
     * @return 账号
     */
    private Map<String, Object> accountLoop(PayType type, PayGroupTypeVo groupType, List<PayGroupAccount> accounts, PayGroupTypeQueryBo bo) {
        BigDecimal money = bo.getUsdMoney();

        //循环收单
        Map<Long, Integer> loops = new HashMap<>();
        if (Objects.equals(groupType.getIsLoop(), 1)) {
            loops = redisAlgorithmKernel.getLoopAccount(groupType.getId());
        }

        PayGroupAccount groupAct = null;
        accounts = accounts.stream().peek(tmp -> {
            if (tmp.getSort() == null) {
                tmp.setSort(0);
            }
        }).sorted(Comparator.comparing(PayGroupAccount::getSort).reversed()).toList();//排序 倒叙
        for (PayGroupAccount obj : accounts) {
            //查询缓存账号
            PayAccount account = payAccountService.selectById(obj.getAccountId());
            //验证收款账号国家策略
            if (account != null && account.getCountryType() != null && StringUtils.isNotEmpty(account.getCountryCode())) {
                R<String> result = payPolicyKernel.validateCountry(account.getCountryType(), account.getCountryCode(), bo.getCountry());
                if (R.isError(result)) {
                    continue;
                }
            }

            //循环收单，收款账号已使用过 并且 收单次数大于等于限制次数
            if (!loops.isEmpty() && loops.containsKey(obj.getAccountId()) && loops.get(obj.getAccountId()) >= groupType.getNextCount()) {
                continue;
            }

            //检查是否小于单笔订单限额
            if (money != null && money.compareTo(BigDecimal.ZERO) > 0
                && obj.getMinMoney() != null && obj.getMinMoney().compareTo(BigDecimal.ZERO) > 0
                && money.compareTo(obj.getMinMoney()) < 0) {
                continue;
            }

            //检查是否大于单笔订单限额
            if (money != null && money.compareTo(BigDecimal.ZERO) > 0
                && obj.getMaxMoney() != null && obj.getMaxMoney().compareTo(BigDecimal.ZERO) > 0
                && money.compareTo(obj.getMaxMoney()) > 0) {
                continue;
            }

            groupAct = obj;
            break;
        }

        if (!loops.isEmpty() && groupAct == null) {
            redisAlgorithmKernel.rmGroupLoop(groupType.getId());
            return accountLoop(type, groupType, accounts, bo);
        }

        if (groupAct == null) {
            return null;
        }

        //记录本次收款账号
        int num = 1;
        if (loops.containsKey(groupAct.getAccountId())) {
            num = loops.get(groupAct.getAccountId()) + 1;
        }
        redisAlgorithmKernel.setLoopAccount(groupType.getId(), groupAct.getAccountId(), num);

        //构建输出对象
        return buildOutGroupAccount(groupAct, type, bo);
    }

    /**
     * 构建输出对象
     *
     * @param groupAct 轮询组账号
     * @param type     平台
     * @param bo       轮询收款账号参数
     * @return Map
     */
    private Map<String, Object> buildOutGroupAccount(PayGroupAccount groupAct, PayType type, PayGroupTypeQueryBo bo) {
        //返回账号
        PayGroupAccountOutVo out = BeanUtils.copy(groupAct, PayGroupAccountOutVo.class);

        //设置收款方式
        if (StringUtils.isNotEmpty(groupAct.getReceiveList())) {
            out.setReceives(JSONArray.parseArray(groupAct.getReceiveList(), PayReceiveVo.class));
        }

        //获取API配置
        ApiConfig apiConfig = ApiConfig.getConfig(type.getConfig(), groupAct.getApiType());

        //查询收款账号
        PayAccount account = payAccountService.selectById(groupAct.getAccountId());
        if (account == null) {
            return null;
        }
        //获取账号配置
        JSONObject actConfig = account.getAccountConfig();

        //获取账号收款方式
        if (apiConfig != null) {
            out.setPayName(apiConfig.getPayName());
            ApiConfig.PayMethod method = ApiConfig.getPayMethod(apiConfig, out.getPayMethod());
            //构建内嵌支付链接
            if (method != null && ((method.getInnerType() != null && innerLinks.contains(method.getInnerType())) || method.getInner())) {
                out.setInnerUrl(buildInnerLink(type, account, apiConfig, method, actConfig, bo));
            }
        }

        //转换成Map
        Map<String, Object> map = MapstructUtils.convert(out);

        //设置自定义字段
        map = setCustomFields(map, groupAct, actConfig);

        map.remove("payTitle");
        map.remove("describe");

        return map;
    }

    /**
     * 设置自定义字段数据
     *
     * @param map          付款信息map
     * @param groupAccount 轮询组账号配置
     * @param account      账号信息
     * @return 增加字段后的map
     */
    @Override
    public Map<String, Object> setCustomFields(Map<String, Object> map, PayGroupAccount groupAccount, PayAccount account) {
        return setCustomFields(map, groupAccount, account.getAccountConfig());
    }

    /**
     * 设置自定义字段数据
     *
     * @param map          付款信息map
     * @param groupAccount 轮询组账号配置
     * @param config       账号配置信息
     * @return 增加字段后的map
     */
    @Override
    public Map<String, Object> setCustomFields(Map<String, Object> map, PayGroupAccount groupAccount, JSONObject config) {

        if (config == null) {
            return map;
        }

        /***************账号自定义字段*********************/
        if (config.containsKey("customConfig")) {
            try {
                JSONObject customConfig = JSONObject.parseObject(config.getString("customConfig"));
                Set<String> fields = customConfig.keySet();
                for (String field : fields) {
                    Object val = customConfig.get(field);

                    //支付描述
                    if (Objects.equals(field, "title") && (
                        Objects.equals(customConfig.getInteger("titleType"), 2)
                            || Objects.equals(customConfig.getInteger("titleType"), 4)
                            || Objects.equals(customConfig.getInteger("titleType"), 5))) {
                        String valStr = val.toString();
                        //随机生成支付描述后缀
                        map.put(field, valStr + StringUtils.generateRandomCode(new Random().nextInt(6, 15)));
                        continue;
                    }

                    map.put(field, val);
                }
            } catch (Exception ignore) {
            }
        }

        /***************应用账号配置自定义字段********************/
        try {
            if (StringUtils.isEmpty(groupAccount.getCustomFields())) {
                return map;
            }

            //自定义字段数据
            JSONObject json = JSONObject.parseObject(groupAccount.getCustomFields());
            if (json == null || json.isEmpty()) {
                return map;
            }

            //应用账号自定义字段配置
            if (!config.containsKey("customFields")) {
                return map;
            }
            List<CustomField> fields = config.getList("customFields", CustomField.class);

            //设置自定义字段
            for (CustomField customField : fields) {
                String fieldName = customField.getValue();

                //标题单独处理
                if (Objects.equals(fieldName, "title") && !map.containsKey("title")) {
                    String title = json.getString("title");
                    if (!title.contains("|")) {
                        map.put(fieldName, title);
                    } else {
                        String[] titles = title.split("\\|");
                        int randomIndex = new Random().nextInt(titles.length);
                        map.put(fieldName, titles[randomIndex]);
                    }
                    continue;
                }

                map.put(fieldName, json.get(fieldName));
            }
        } catch (Exception ignore) {
        }

        return map;
    }

    /**
     * 构建内嵌支付链接
     *
     * @param account 账号域名
     * @param apiConfig     api配置
     * @param method        支付方式
     * @param actConfig     账号配置
     * @param bo            轮询收款账号参数
     * @return 内嵌支付链接
     */
    public String buildInnerLink(PayType type, PayAccount account, ApiConfig apiConfig, ApiConfig.PayMethod method, JSONObject actConfig, PayGroupTypeQueryBo bo) {
        if (apiConfig == null) {
            return null;
        }
        String plugin = apiConfig.getPlugin();
        if (StringUtils.isEmpty(plugin)) {
            return null;
        }

        //是否直连内嵌方式
        if (method != null && (Objects.equals(method.getInnerType(), 2) || method.getInnerPay())) {
            String innerUrl = gatewayDomain + plugin + ".html";
            String str = innerUrl + JwtUtils.secretStr;
            return innerUrl + "?token=" + EncryptUtils.encryptByMd5(str) + "&timestamp=" + (System.currentTimeMillis() / 1000);
        }

        String accountDomain = account.getDomain();
        String baseUrl;
        if (actConfig != null && actConfig.containsKey("innerUrl")) {
            //如果账号配置了内嵌地址，则使用账号配置的地址
            baseUrl = accountDomain + actConfig.getString("innerUrl");
        } else {
            baseUrl = accountDomain + (plugin.endsWith("/") ? plugin : plugin + "/") + "preload";
        }

        String innerUrl = baseUrl + "?timestamp=" + (System.currentTimeMillis() / 1000);


        //如果内嵌类型 = 0  或者 stripe的内嵌
        if (method != null && ((type.getCode().equals("stripe") && Objects.equals(method.getValue(), "3")) || Objects.equals(method.getInnerType(), 0))) {
            innerUrl += "&total=" + bo.getTotal() + "&currency=" + bo.getCurrency() + "&country=" + (StringUtils.isEmpty(bo.getCountry()) ? "" : bo.getCountry());

            String sessionId = EncryptUtils.encryptBySm4( account.getAccountId() + "", EncryptUtils.PRIVATE_KEY).replaceAll("=","");
            innerUrl += "&ao=" + sessionId;

            String str = innerUrl + 1 + JwtUtils.secretStr;
            return innerUrl + "&token=" + EncryptUtils.encryptByMd5(str);
        }

        //其他
        String str = gatewayDomain + innerUrl + JwtUtils.secretStr;
        return innerUrl + "&token=" + EncryptUtils.encryptByMd5(str);
    }

    /**
     * 构建内嵌支付链接
     *
     * @param accountDomain 账号域名
     * @param apiConfig     api配置
     * @param bo            轮询收款账号参数
     * @return 内嵌支付链接
     */
//    public String requestInnerLink(String accountDomain, ApiConfig apiConfig, PayGroupTypeQueryBo bo) {
//        if (apiConfig == null) {
//            return null;
//        }
//        String plugin = apiConfig.getPlugin();
//        if (StringUtils.isEmpty(plugin)) {
//            return null;
//        }
//
//        String url = accountDomain + plugin + "/preload/url";
//        JSONObject json = new JSONObject();
//        json.put("domain", accountDomain);
//        json.put("total", bo.getTotal());
//        json.put("currency", bo.getCurrency());
//        json.put("country", bo.getCountry());
//        String str = new RequestUtils().doPostJsonByToken(url, json.toJSONString());
//
//        JSONObject jsonObject = JSONObject.parseObject(str);
//
//        return ;
//    }

    /**
     * 查询轮询组支付类型关联
     *
     * @param id 主键
     * @return 轮询组支付类型关联
     */
    @Override
    public PayGroupTypeVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询轮询组支付类型关联列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 轮询组支付类型关联分页列表
     */
    @Override
    public TableDataInfo<PayGroupTypeVo> queryPageList(PayGroupTypeBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<PayGroupType> lqw = buildQueryWrapper(bo);
        Page<PayGroupTypeVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的轮询组支付类型关联列表
     *
     * @param bo 查询条件
     * @return 轮询组支付类型关联列表
     */
    @Override
    public List<PayGroupTypeVo> queryList(PayGroupTypeBo bo) {
        LambdaQueryWrapper<PayGroupType> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<PayGroupType> buildQueryWrapper(PayGroupTypeBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<PayGroupType> lqw = Wrappers.lambdaQuery();
        lqw.orderByDesc(PayGroupType::getSort);
        lqw.eq(bo.getGroupId() != null, PayGroupType::getGroupId, bo.getGroupId());
        lqw.eq(bo.getTypeId() != null, PayGroupType::getTypeId, bo.getTypeId());
        return lqw;
    }

    /**
     * 新增轮询组支付类型关联
     *
     * @param bo 轮询组支付类型关联
     * @return 是否新增成功
     */
    @Override
    public R<?> insertByBo(PayGroupTypeBo bo) {
//        if (baseMapper.selectCount(new LambdaQueryWrapper<PayGroupType>().eq(PayGroupType::getGroupId, bo.getGroupId()).eq(PayGroupType::getTypeId, bo.getTypeId())) > 0) {
//            return R.fail("此平台已填加,请勿重复此操作");
//        }

        PayGroupType add = MapstructUtils.convert(bo, PayGroupType.class);
        PayType type = payTypeMapper.selectById(add.getTypeId());
        if (type == null) {
            return R.fail("所选平台不存在");
        }
        add.setCode(type.getCode());
        add.setCreateTime(DateUtils.getNowDate());
        if (Objects.equals(add.getIsLoop(), 0)) {
            add.setIsFixed(0);
        }
        boolean flag = baseMapper.insert(add) > 0;

        return flag ? R.ok() : R.fail();
    }

    /**
     * 修改轮询组支付类型关联
     *
     * @param bo 轮询组支付类型关联
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(PayGroupTypeBo bo) {
        PayGroupType update = MapstructUtils.convert(bo, PayGroupType.class);
        PayType type = payTypeMapper.selectById(update.getTypeId());
        if (type == null) {
            return false;
        }
        update.setCode(type.getCode());
        update.setUpdateTime(DateUtils.getNowDate());

        if (Objects.equals(update.getIsLoop(), 0)) {
            update.setIsFixed(0);
        }

        boolean flag = baseMapper.updateById(update) > 0;
        if (flag && bo.getPolicyId() == null) {
            baseMapper.update(Wrappers.<PayGroupType>lambdaUpdate().eq(PayGroupType::getId, bo.getId()).set(PayGroupType::getPolicyId, null));
        }
        return flag;
    }


    /**
     * 校验并批量删除轮询组支付类型关联信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }

      List<PayGroupType> groupTypes = baseMapper.selectBatchIds(ids);
        boolean tag = baseMapper.deleteByIds(ids) > 0;
        if(tag) {
            groupTypes.forEach(groupType -> {
                LambdaQueryWrapper<PayGroupAccount> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(PayGroupAccount::getGroupId, groupType.getGroupId());
                queryWrapper.eq(PayGroupAccount::getGroupTypeId, groupType.getId());
                payGroupAccountService.deleteByQuery(queryWrapper);
            });
        }
        return tag;
    }
}
